package org.lsp.io.files;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.lsp.io.logger.SysLogger;
import org.lsp.io.logger.SysLogger.Level;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Methods for performing data CRUD on XML files.
 * 
 * @author See <a href="the Google Code page of the easy-utils
 *         library">http://code.google.com/p/easy-utils/</a> for collaborators
 *         and other information.
 */
public abstract class XML {

	public static final ArrayList<HashMap<String, String>> getAllOfType(
			final String type, final File targetFile) {
		final ArrayList<HashMap<String, String>> ret = new ArrayList<>();
		FileLocker.getLock(targetFile);
		DocumentBuilder docBuilder;
		Document doc = null;

		try {
			docBuilder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			doc = docBuilder.parse(targetFile);
		} catch (SAXException | IOException | ParserConfigurationException ex) {
			SysLogger.log(ex, "ERR2", Level.CRASH);
		}

		final NodeList parent = doc.getElementsByTagName(type);

		NamedNodeMap map;
		HashMap<String, String> attr;
		for (int i = 0; i < parent.getLength(); i++) {
			map = ((Element) parent.item(i)).getAttributes();
			Attr curr;
			attr = new HashMap<>();
			for (int j = 0; j < map.getLength(); j++) {
				curr = (Attr) map.item(j);
				attr.put(curr.getName(), curr.getValue());
			}

			ret.add(attr);
		}
		FileLocker.releaseLock(targetFile);
		return ret;
	}

	public static final void updateNodeInfo(final String nodeType,
			final String idAttrValue, final String idAttrName,
			final File destFile, final ArrayList<String[]> attributes) {
		FileLocker.getLock(destFile);
		if (attributes != null) {
			for (final String[] name : attributes) {
				if (name.length != 2) {
					throw new IllegalArgumentException(
							"Invalid attribute combination");
				}
			}
		}

		/*
		 * XML DATA EDITION - BEGINNING
		 */
		DocumentBuilder docBuilder;
		Document doc = null;

		try {
			docBuilder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			doc = docBuilder.parse(destFile);
		} catch (SAXException | IOException | ParserConfigurationException ex) {
			SysLogger.log(ex, "ERR3", Level.CRASH);
		}

		final NodeList elements = doc.getElementsByTagName(nodeType);
		Node node = null;

		for (int i = 0; i < elements.getLength(); i++) {
			if (idAttrValue.matches(((Element) elements.item(i))
					.getAttribute(idAttrName))) {
				node = elements.item(i);
				break;
			}
		}
		final NamedNodeMap map = node.getAttributes();

		if (attributes != null) {
			for (final String[] x : attributes) {
				map.getNamedItem(x[0]).setTextContent(x[1]);
			}
		}
		/*
		 * XML DATA EDITION - END
		 */

		/*
		 * XML DATA DUMP - BEGINNING
		 */
		Transformer transformer = null;
		try {
			transformer = TransformerFactory.newInstance().newTransformer();
		} catch (final TransformerConfigurationException ex) {
			SysLogger.log(ex, "ERR4", Level.CRASH);
		}
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");

		final StreamResult result = new StreamResult(new StringWriter());
		final DOMSource source = new DOMSource(doc);
		try {
			transformer.transform(source, result);
		} catch (final TransformerException ex) {
			SysLogger.log(ex, "ERR5", Level.CRASH);
		}

		final String xmlString = result.getWriter().toString();
		try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
				destFile))) {
			bufferedWriter.write(xmlString);
		} catch (final IOException ex) {
			SysLogger.log(ex, "ERR6", Level.CRASH);
		}
		/*
		 * XML DATA DUMP - END
		 */
		FileLocker.releaseLock(destFile);
	}
}